/* uDig - User Friendly Desktop Internet GIS client
 * http://udig.refractions.net
 * (C) 2004-2008, Refractions Research Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package symbology;

import java.util.ArrayList;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;


/**
 * A ParseApl object parses an ESRI ArcPad Layer file.
 * <p>
 * The layer file contains XML describing how a layer is to be symbolized.
 * </p>
 * @author John
 * @since 1.1.0
 */
public class ParseApl extends ParseXml {

    /**
     * processElement processes each XML element in the APL file, building up a corresponding 
     * Symbolizer object.
     */
    @Override
    public Object processElement( String tag, Attributes attributes, String characters,
            ArrayList<Object> objects ) throws SAXException {
        
        System.out.printf("depth=%d  tag=%s  objects=[", stack.size(), tag);
        for (Object o: objects)
            System.out.printf(" %s", o.getClass().getName());
        System.out.printf(" ]\n");
        
        
        if (tag.equals("VALUEMAPRENDERER")) {
            
            // Get the GIS feature attribute name to use in the lookup
            String s = attributes.getValue("lookupfield");
            if (s == null) 
                throw new SAXException("VALUEMAPRENDERER: lookupfield is missing");
        
            // Create a RANGE Symbolizer
            RangeSymbolizer rangeSymbolizer = new RangeSymbolizer(s);
            
            // Add in each of the Range objects we just parsed
            for (Object obj : objects)
                if (obj instanceof Range)
                    rangeSymbolizer.add( (Range)obj );
                else
                    throw new SAXException("VALUEMAPRENDERER: bad RANGE element");
            
            // return the RangeSymbolizer
            return rangeSymbolizer; 
        }
        
            
        // CASE: RANGE element
        else if (tag.equals("RANGE")) {
            
            // Get the min and max value
            double min = getDouble(attributes, "lower", Double.MIN_VALUE);
            double max = getDouble(attributes, "upper", Double.MAX_VALUE);
            
            // get the Symbolizer object
            if (objects.size() != 1)
                throw new SAXException("RANGE: needs a single symbol");
            Object o = objects.get(0);
                
            // We should have a symbolizer
            if ( ! (o instanceof Symbolizer))
                throw new SAXException("RANGE: bad symbol");
            
            // Create a range entry
            return new Range(min, max, (Symbolizer)o);
        }
            
        // CASE: SIMPLEPOLYGONSYMBOL
        else if (tag.equals("SIMPLEPOLYGONSYMBOL")) {
            
            double width = getDouble(attributes, "boundarywidth", 1);
            Color color = getColor(attributes, "boundarycolor", "boundarytransparency", Color.BLACK);
            Color fill = getColor(attributes, "fillcolor", "filltransparency", Color.BLACK);
            
            return new AreaSymbol(color, width, fill);
        }
         
        // CASE: Symbology
        else if (tag.equals("SYMBOLOGY")) {
            
            // if no symbolizers, then done
            if (objects.size() == 0)
                return null;
            
            // if a single symbolizer, then return it
            if (objects.size() == 1) {
                Object o = objects.get(0);
                if (o instanceof Symbolizer) return o;
                else   throw new SAXException("SYMBOLOGY: Bad renderer");
            }
            
            // otherwise, create a symbolizer group
            GroupSymbolizer group = new GroupSymbolizer();
            for (Object o: objects)
                if (o instanceof Symbolizer)
                    group.add((Symbolizer)o);
                else
                    throw new SAXException("SYMBOLOGY: Bad renderer");
           
            return group;  
        }
        
        // CASE: SIMPLELABELRENDERER
        else if (tag.equals("SIMPLELABELRENDERER")) {
            
            // Is it visible?
            String visible = getString(attributes, "visible", "true");
            if (!visible.equals("true"))
                return null;
            
            // Get the label field
            String fieldName = attributes.getValue("field");
            if (fieldName == null)
                throw new SAXException("SIMPLELABELRENDERER: 'field=' is required");
            
            // Get the character style
            String font = getString(attributes, "font", "Arial");
            double size = getDouble(attributes, "fontsize", 10.0);
            String weight = getString(attributes, "fontstyle", "normal");
            Color color = getColor(attributes, "fontcolor", "fonttransparency", Color.BLACK);
            Color highlight = Color.NONE;
            
            // Create a label symbolizer with the corresponding text symbol
            TextSymbol textSymbol = new TextSymbol("", font, weight, size, color, highlight);
            return new LabelSymbolizer(fieldName, textSymbol);
            
        }
        
        // CASE: Truetype Markers - a single character represents a symbol
        else if (tag.equals("TRUETYPEMARKERSYMBOL")) {
            
            // Fetch attributes we're interested in
            int character = (int)getDouble(attributes, "character", -1);
            if (character == -1)
                throw new SAXException("TRUETYPEMARKERSYMBOL: must specify character=");
            String font = getString(attributes, "font", "Arial");
            Color color = getColor(attributes, "fontcolor", "fonttransparency", Color.RED);
            String weight = getString(attributes, "fontstyle", "regular");
            double size = getDouble(attributes, "fontsize", 10.0);
            
            // Special handling for ESRI markers. Translate to a MarkerSymbol filled with a single color
            if (font.equals("ESRI Default Marker")) {
                if (character == 33) 
                    return new MarkerSymbol(MarkerSymbol.Type.CIRCLE, size, color, 1, color);
                throw new SAXException("TRUETYPEMARKERSYMBOL: character " + character + " does not translate to a marker symbol");
            } 
            
            // We haven't implemented truetype symbols, so throw an exception.
            throw new SAXException("TRUETYPEMARKERSYMBOL: true type markers not implemented yet");
        }
        
        
        // Default case: return the first object in the list. (Throw exception if more than one?) 
        else {
            if (objects.isEmpty())  return null;
            else                    return objects.get(0);
        }      
    }
    
    
    /**
     * getColor gets color and opacity information from the current element in an ESRI XML file
     * @param attributes - the attributes of the current XML element
     * @param colorField - the name of the attribute containing color information
     * @param opacityField - the name of the attribute containing opacity
     * @param def - the default color if fields are not present
     * @return a Color object
     * @throws SAXException if can't parse the color information
     */
    static Color getColor(Attributes attributes, String colorField, String opacityField, Color def) throws SAXException {
        
        // Get the opacity
        double opacity = getDouble(attributes, opacityField, 1.0);
        
        // Get the ESRI string representing color
        String s = attributes.getValue(colorField);
        if (s == null) return new Color(def, opacity);
        
        // parse the ESRI color string (3 comma separated integers)
        int red, green, blue;
        try {
            String[] parts = s.split(",");
            red = Integer.parseInt(parts[0]);
            green = Integer.parseInt(parts[1]);
            blue = Integer.parseInt(parts[2]);
        } 
        catch (Exception e) {
            throw new SAXException("Unable to parse color string '"+s+"'");
        }
        
        // return the color
        return new Color(red, green, blue, opacity);
           
    }
}
